﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using LittleBirdLib;

namespace LittleBirdAuction.Core
{
    public class Trainer
    {

        uint[] _address = new uint[(int)AuctionObjects.OBJECT_COUNT];
        uint _combo_count;
        uint[] _combo_addresses = new uint[(int)AuctionConst.UI_COMBO_ROW_LIMIT];
        uint[] _ui_addresses = new uint[(int)AuctionObjects.OBJECT_COUNT];
        uint _ui_root;
        bool _trained;

        public uint _d3_base
        {
            get;
            private set;
        }

        public uint _bnet_base
        {
            get;
            private set;
        }

        public Trainer()
        {
            _d3_base = (uint)(Globals.mem.GetProcessModule(AuctionConst._MODULE_D3).BaseAddress.ToInt32());
            _bnet_base = (uint)(Globals.mem.GetProcessModule(AuctionConst._MODULE_D3).BaseAddress.ToInt32());

            _trained = true;
        }
        //------------------------------------------------------------------------
        /*
            checks patch changes to see where things broke. build and run in debug
            mode. step over each call while checking each value is accurate under
            conditions specified in the comments.

            do not run this under normal operation
        */
        public bool CheckPatchChanges()
        {
            UiObject uiObject = new UiObject();
            uint gold = 0;
            string account = string.Empty;
            DiabloItem item = new DiabloItem();
            uint count1 = 0, count2 = 0;
            bool active = false;

            // gold
            // @anywhere in AH
            if (!ReadGold(ref gold))
                return false;

            // account name
            // @anywhere in AH
            if (!ReadAccount(ref account))
                return false;

            // search list item
            // @AH search results
            // click first item, step over, and check bid/buyout values
            if (!ReadListItem(0, ref item))
                return false;

            // auction selling item
            // @AH auction selling list
            // click first item, step over, and check bid/buyout values
            if (!ReadSellItem(0, ref item))
                return false;

            // auction selling ready status
            // @AH auction selling list
            // verify sell count is correct
            if (!ReadSellStatus(ref active) || !active)
                return false;

            // frame count
            // @anywhere
            if (!ReadFrameCount(ref count1))
                return false;
            // wait 1 second
            System.Threading.Thread.Sleep(1000);
            // if 0 or over 10k fps something is wrong
            if (!ReadFrameCount(ref count2) || (count2 - count1) == 0 || (count2 - count1) > 10000)
                return false;

            // login status
            // @anywhere logged in
            if (!ReadLoginStatus(ref active) || !active)
                return false;

            // ui objects
            // @AH with an error popup open (example: 10 auction sell limit popup)
            for (int i = 0; i < (int)AuctionObjects.OBJECT_COUNT; i++)
                if (!_ReadUiObject(ref uiObject, i))
                    return false;

            return true;
        }

        //------------------------------------------------------------------------
        public bool CheckTrained()
        {
            // check stat first
            return _trained;
        }

        //------------------------------------------------------------------------
        public bool WriteInputText(int id, string text)
        {
            UiObject ui_object = new UiObject();
            string ui_text = string.Empty;

            // read object
            if (!_ReadUiObject(ref ui_object, id))
                return false;

            // copy text
            Tools.StrNCpy(ref ui_text, text, AuctionConst._TEXT_STANDARD_LIMIT);

            // write input
            Globals.mem.WriteText((IntPtr)(ui_object.addr2_value), ui_text, Encoding.UTF8);

            return true;
        }

        //------------------------------------------------------------------------
        public bool ReadInputText(int id, ref string text)
        {
            UiObject   ui_object = new UiObject();
            string  ui_text = string.Empty;

            // read object
            if(!_ReadUiObject(ref ui_object, id))
                return false;

            // read input
            ui_text = Globals.mem.ReadMemoryAsString(ui_object.addr2_value, (int)AuctionConst._TEXT_STANDARD_LIMIT).ToString();

            // copy text
            Tools.StrNCpy(ref text, ui_text, ui_text.Length);

            return true;
        }

        //------------------------------------------------------------------------
        public bool WriteComboIndex(int id, uint index)
        {
            UiObject uiObj = new UiObject();
            uint address = 0;

            // verify
            if (!_ReadUiObject(ref uiObj, id, ref address))
                return false;

            // write
            Globals.mem.WriteMemoryAsInt(address + (uint)Marshal.OffsetOf(typeof(UiObject), "n5"), (int)index);

            return true;
        }

        //------------------------------------------------------------------------
        public bool _ReadUiObject(ref UiObject uiObj, int id, ref uint address)
        {
            uint cache_address = _ui_addresses[id];
            bool dirty = false;

            // must be trained
            if (!_trained)
                return false;

            // if have cached address
            if (cache_address > 0)
            {
                uiObj = Globals.mem.UnsafeReadToStruct<UiObject>(address);

                // validate3
                if (!IsValidUiObject(uiObj, id))
                    dirty = true;
            }
            else
                dirty = true;

            // find object and cache
            if (dirty && !_RecurseUiObject(ref uiObj, ref cache_address, id))
                return false;

            address = cache_address;

            return true;
        }

        public bool _ReadUiObject(ref UiObject uiObj, int id)
        {
            uint address = 0;

            return _ReadUiObject(ref uiObj, id, ref address);
        }

        public unsafe bool ReadComboIndex(int id, ref uint index, ref uint count)
        {
            UiObject uiObj = new UiObject();

            // verify
            if (!_ReadUiObject(ref uiObj, id))
                return false;

            index = uiObj.n5[0];
            count = uiObj.n5[3];

            return true;
        }

        public bool ReadComboString(int id, string str, bool strip)
        {
            UiObject uiObj = new UiObject();
            uint count = 0;
            string found = string.Empty;
            string string1 = string.Empty;

            // read ui object
            if (!_ReadUiObject(ref uiObj, id))
                return false;

            uiObj = Globals.mem.UnsafeReadToStruct<UiObject>(uiObj.addr3_child);

            // read input
            found = Globals.mem.ReadMemoryAsString(uiObj.addr2_value, (int)AuctionConst._TEXT_COMBO_LIMIT).ToString();


            // strip formatting if any else use original string
            if (!strip || Tools.StrFormatRead(ref count, found, AuctionConst._FORMAT_COMBO_TEXT, string1, str, string1) > 0 || count != 2)
            {
                Tools.StrNCpy(ref str, found, str.Length);
            }

            return true;
        }

        public bool ReadComboRowBegin()
        {
            UiObject uiObj = new UiObject();

            _combo_count = 0;

            // top layer
            if(!_ReadUiObject(ref uiObj, (int)AuctionObjects.OBJECT_TOP))
                return false;

            // drop down
            if(!_ReadUiChild(ref uiObj, AuctionConst._HINT_DROPDOWN, ref uiObj))
                return false;

            // drop down content
            if (!_ReadUiChild(ref uiObj, AuctionConst._HINT_DROPDOWNCONTENT, ref uiObj))
                return false;

            // drop down content stack
            if (!_ReadUiChild(ref uiObj, AuctionConst._HINT_DROPDOWNCONTENTSTACK, ref uiObj))
                return false;

            // read item list
            uint count = uiObj.addr1_count = Tools.Min(uiObj.addr1_count, AuctionConst.UI_COMBO_ROW_LIMIT);

            if (!Globals.mem.ReadMemory(uiObj.addr1_children, _combo_addresses.Length, out _combo_addresses))
                return false;

            _combo_count = count;

            return true;
        }

        public bool ReadComboRow(uint index, string str, ref double y)
        {
            UiObject uiObj;

            // must be ready
            if (!_trained)
                return false;

            // end of list
            if (index >= _combo_count)
                return false;

            uiObj = Globals.mem.UnsafeReadToStruct<UiObject>(_combo_addresses[index]);
            // read item object

            // get Y coordinate
            if (uiObj.y1 > 0.0f && uiObj.y2 > 0.0f)
            {
                float height = (uiObj.y2 - uiObj.y1);
                float position = height / 2.0f + uiObj.y1;
                y = position / AuctionConst._COORD_HEIGHT;
            }

            str = Globals.mem.ReadMemoryAsString(uiObj.addr2_value, (int)AuctionConst._TEXT_COMBO_LIMIT).ToString();

            return true;
        }

        public bool ReadComboRowCount(ref uint count)
        {
            // require combo state
            if (_combo_count == 0)
                return false;

            count = _combo_count;

            return true;
        }

        //------------------------------------------------------------------------
        public bool ReadHoverStatus(ref bool status)
        {
            UiObject ui_object = new UiObject();

            // read ui object
            if (!_ReadUiObject(ref ui_object, (int)AuctionObjects.OBJECT_TOOLTIP_NAME))
                return false;

            // determine status
            status = (ui_object.visible != 0);

            return true;
        }

        //------------------------------------------------------------------------
        bool ReadHoverItem(ref DiabloItem item)
        {
            // name rarity type dps/armor
            if (!_ReadHoverItemHeaders(ref item))
                return false;

            // stats (can be empty)
            if (!_ReadHoverItemStats(ref item.stats))
                return false;

            // sockets (can be empty)
            if (!_ReadHoverItemSockets(ref item.sockets))
                return false;

            // strip gem bonuses
            Support.StripGemBonuses(ref item);

            // build text item id
            Support.BuildItemHash(ref item);

            // must have something!
            return (!string.IsNullOrEmpty(item.name) || item.rarity != RarityId.RARITY_UNKNOWN ||
                item.dpsarmor > 0 || item.stats.Count > 0 || item.sockets.Count > 0);
        }

        //------------------------------------------------------------------------
        bool ClearHoverItem()
        {
            // hide key objects
            if (!_WriteUiObject((int)AuctionObjects.OBJECT_TOOLTIP_STATS, false) ||
                !_WriteUiObject((int)AuctionObjects.OBJECT_TOOLTIP_DPSARMOR, false) ||
                !_WriteUiObject((int)AuctionObjects.OBJECT_TOOLTIP_SOCKET0, false) ||
                !_WriteUiObject((int)AuctionObjects.OBJECT_TOOLTIP_SOCKET1, false) ||
                !_WriteUiObject((int)AuctionObjects.OBJECT_TOOLTIP_SOCKET2, false) ||
                !_WriteUiObject((int)AuctionObjects.OBJECT_TOOLTIP_NAME, false))
                return false;

            // headers
            if (!_ClearHoverItemHeaders())
                return false;

            // stats
            if (!_ClearHoverItemStats())
                return false;

            // sockets
            if (!_ClearHoverItemSockets())
                return false;

            return true;
        }


        //------------------------------------------------------------------------
        public unsafe bool ReadListItem(uint index, ref DiabloItem item)
        {
            AhListRoot root = new AhListRoot();
            AhListAuction item_object;

            // read list object
            if (!_ReadListRoot(ref root))
                return false;

            int size = sizeof(AhListAuction);
            uint address = root.auction_items + index * (uint)(size);
            item_object = Globals.mem.UnsafeReadToStruct<AhListAuction>(address);

            // hint checks
            string hint = Marshal.PtrToStringAnsi((IntPtr)(item_object.d3item));
            if (!hint.Equals(AuctionConst._HINT_LISTITEM_ITEM))
                return false;

            // cbid, mbid
            if (item_object.bid1 > 0)
            {
                item.bid_current = item_object.bid1;
                item.bid_max = item_object.bid2;
            }
            else
                item.bid_current = item.bid_max = item_object.bid2;

            // id
            item.id = item_object.id;

            // buyout
            item.buyout = item_object.buyout;

            // time
            ulong expire = item_object.time / 1000;
            ulong current = LittleBirdLib.Utilities.DateTimeTool.GetTimeMs();


            item.time_remaining = expire > current ? (uint)(expire - current) : 0;
            item.time_expiring = (uint)(expire / 1000);

            // verify
            return (item.id > 0 && item.time_expiring > 0);
        }


        //------------------------------------------------------------------------
        bool ReadListCount(ref uint count)
        {
            AhListRoot root = new AhListRoot();

            // read list object
            if (!_ReadListRoot(ref root))
                return false;

            // get values
            count = root.auction_count;

            return true;
        }

        //------------------------------------------------------------------------
        bool ReadListNextStatus(ref bool status)
        {
            UiObject ui_object = new UiObject();

            // read ui object
            if (!_ReadUiObject(ref ui_object, (int)AuctionObjects.OBJECT_LBUTTON_PAGENEXT))
                return false;

            // determine status
            status = (ui_object.visible > 0 && ui_object.anim_state >= 16);

            return true;
        }


        // selling list
        //------------------------------------------------------------------------
        public unsafe bool ReadSellItem(uint index, ref DiabloItem item)
        {
            AhListRoot root = new AhListRoot();
            AhListSell item_object;

            // read list object
            if (!_ReadSellRoot(ref root))
                return false;

            // read item object
            uint address = root.sell_items + index * (uint)(sizeof(AhListSell));
            item_object = Globals.mem.UnsafeReadToStruct<AhListSell>(address);

            // id
            item.id = item_object.id;

            // start, current, buyout
            item.bid_start = item_object.bid1;
            item.bid_current = item_object.bid2;
            item.buyout = item_object.buyout;


            // time
            ulong expire = item_object.xtime / 1000;
            ulong current = LittleBirdLib.Utilities.DateTimeTool.GetTimeMs();

            item.time_remaining = expire > current ? (uint)(expire - current) : 0;
            item.time_expiring = (uint)(expire / 1000);

            // verify
            return (item.bid_start > 0 && item.time_expiring > 0);
        }


        //------------------------------------------------------------------------
        public bool ReadSellCount(ref uint count)
        {
            AhListRoot root = new AhListRoot();

            // read list object
            if (!_ReadSellRoot(ref root))
                return false;

            // get values
            count = root.sell_count;

            return true;
        }


        //------------------------------------------------------------------------
        public bool ReadSellStatus(ref bool active)
        {
            AhGlobal2 ahGlobalObj;
            uint count = 0;

            // get list root object from static chain
            uint address = Globals.mem.ReadMemory(_d3_base, AuctionConst._CHAIN_SELLCOUNT);
            if (address == 0)
                return false;

            // read list object
            ahGlobalObj = Globals.mem.UnsafeReadToStruct<AhGlobal2>(address);


            // read current list count
            if (!ReadSellCount(ref count))
                return false;

            // compare to global count
            active = (count == ahGlobalObj.sellcount);

            return true;
        }


        // etc
        //------------------------------------------------------------------------
        bool ReadButtonStatus(ref ButtonStatus status, int button_id)
        {
            UiObject ui_object = new UiObject();

            // read ui object
            if (!_ReadUiObject(ref ui_object, button_id))
                return false;

            // determine button busy status
            if (ui_object.visible > 0)
            {
                switch (ui_object.anim_state)
                {
                    case 21:
                    case 37:
                        status = ButtonStatus.BUTTON_ENABLED;
                        break;
                    case 20:
                    case 36:
                        status = ButtonStatus.BUTTON_HIGHLIGHTED;
                        break;
                    case 34:
                    default:
                        status = ButtonStatus.BUTTON_DISABLED;
                        break;
                }
            }
            else
                status = ButtonStatus.BUTTON_HIDDEN;

            return true;
        }

        //------------------------------------------------------------------------
        bool ReadPopupStatus(ref bool active, string text, int popup_id)
        {
            UiObject ui_object = new UiObject();

            // special case
            if (popup_id == (int)AuctionObjects.OBJECT_POPUP_ERROR)
            {
                // read base ui object
                if (!_ReadUiObject(ref ui_object, (int)AuctionObjects.OBJECT_NOTIFICATIONS))
                    return false;

                // fail if not visible
                if (ui_object.visible <= 0)
                    return false;
            }

            // read popup
            if (!_ReadUiObject(ref ui_object, popup_id))
                return false;

            text = Globals.mem.ReadMemoryAsString(ui_object.addr2_value, (int)AuctionConst._TEXT_POPUP_LIMIT).ToString();
            // read text
            if (ui_object.visible>0 && !string.IsNullOrEmpty(text))
                return false;

            // active either visible
            active = (ui_object.visible != 0);

            return true;
        }

        //------------------------------------------------------------------------
        bool ReadLoginStatus(ref bool active)
        {
            int status = Globals.mem.ReadMemoryAsInt(_d3_base + AuctionConst._BASEADDRESS_LOGGEDIN);

            // set status
            active = (status > 0);

            return true;
        }

        //------------------------------------------------------------------------
        bool ReadAuctionMainStatus(ref bool active)
        {
            UiObject ui_object = new UiObject();

            // read ui object
            if (!_ReadUiObject(ref ui_object, (int)AuctionObjects.OBJECT_MAIN_AUCTION))
                return false;

            // check auction main status
            active = (ui_object.visible != 0);

            return true;
        }

        //------------------------------------------------------------------------
        public bool ReadFrameCount(ref uint count)
        {
            // get frame count
            count = Globals.mem.ReadMemoryAsUint(_d3_base + AuctionConst._BASEADDRESS_FRAMECOUNT);
            return true;
        }


        //------------------------------------------------------------------------
        public bool ReadGold(ref uint amount)
        {
            AhGlobal1 ah_global;

            // get list root object from static chain
            uint address = Globals.mem.ReadMemory(_d3_base, AuctionConst._CHAIN_GOLD);

            if (address == 0)
                return false;

            ah_global = Globals.mem.UnsafeReadToStruct<AhGlobal1>(address);

            // get total gold amount
            amount = ah_global.gold;

            return true;
        }


        //------------------------------------------------------------------------
        public bool ReadAccount(ref string account)
        {
            // get list root object from static chain
            uint address = Globals.mem.ReadMemory(_d3_base, AuctionConst._CHAIN_ACCOUNT);

            if (address == 0)
                return false;

            try
            {
                // read list object
                account = Globals.mem.ReadMemoryAsString(address, AuctionConst.BUFF_LEN).ToString();

                return true;
            }
            catch (Exception ex)
            {
                LittleBirdLogging.LittleBirdLogger.Instance.Error(ex.ToString());
                return false;
            }
        }

        // private
        //------------------------------------------------------------------------
        private bool _ReadHoverItemHeaders(ref DiabloItem item)
        {
            UiObject        ui_object = new UiObject();
            string ui_text = string.Empty;
            string string1 = string.Empty;
            uint count = 0;

            // read rariity/name
            if(!_ReadUiObject(ref ui_object, (int)AuctionObjects.OBJECT_TOOLTIP_NAME) || ui_object.visible <= 0)
                return false;

            ui_text = Globals.mem.ReadMemoryAsString(ui_object.addr2_value, (int)AuctionConst._TEXT_STANDARD_LIMIT).ToString();

            if( string.IsNullOrEmpty(ui_text) || ui_text.Equals(AuctionConst._CLEAR_CHAR))
                return false;

            ui_text = ui_text.ToUpper();

            if(Tools.StrFormatRead(ref count, ui_text, AuctionConst._FORMAT_ITEM_NAME, string1, item.name) > 0 || count != 2)
                return false;

            IdEnum idEnumItem;
           AuctionConst.ITEM_RARITY_COLORS.FindObject(out idEnumItem , string1);

            item.rarity = (RarityId)idEnumItem.Value;

            // read heading/type
            if(!_ReadUiObject(ref ui_object, (int)AuctionObjects.OBJECT_TOOLTIP_TYPE) || ui_object.visible<=0 )
                return false;

            ui_text = Globals.mem.ReadMemoryAsString(ui_object.addr2_value, (int)AuctionConst._TEXT_STANDARD_LIMIT).ToString();

            if( string.IsNullOrEmpty(ui_text) || ui_text.Equals(AuctionConst._CLEAR_CHAR))
                return false;

            if(Tools.StrFormatRead(ref count, ui_text, AuctionConst._FORMAT_ITEM_HEADING, string1, item.heading) > 0 && count == 2)
                Tools.StrFormatRead(ref count, item.heading, AuctionConst._FORMAT_ITEM_TYPE, string1, item.type);

            // read item dps/armor
            if(!_ReadUiObject(ref ui_object, (int)AuctionObjects.OBJECT_TOOLTIP_DPSARMOR))
                return false;

            if(ui_object.visible > 0)
            {
                ui_text = Globals.mem.ReadMemoryAsString(ui_object.addr2_value, (int)AuctionConst._TEXT_STANDARD_LIMIT).ToString();

                if( !string.IsNullOrEmpty(ui_text) && !ui_text.Equals(AuctionConst._CLEAR_CHAR))
                {
                    long dps = 0;

                    Tools.StrToNumber(ui_text, out dps);

                    item.dpsarmor = (ulong)dps;
                }
            }

            // read shield block%/range or weapon AS/range
            if(!_ReadUiObject(ref ui_object, (int)AuctionObjects.OBJECT_TOOLTIP_ASDAMAGE))
                return false;

            if(ui_object.visible > 0)
            {
                ui_text = Globals.mem.ReadMemoryAsString(ui_object.addr2_value, (int)AuctionConst._TEXT_STANDARD_LIMIT).ToString();

                if( string.IsNullOrEmpty(ui_text) || ui_text.Equals(AuctionConst._CLEAR_CHAR))
                    return false;

                Tools.StrFormatRead(ref count, ui_text, AuctionConst._FORMAT_SHIELD, item.block_chance, string1, item.block_min, item.block_max, string1);
                Tools.StrFormatRead(ref count, ui_text, AuctionConst._FORMAT_WEAPON, item.damage_min, item.damage_max, string1, item.damage_aps, string1);
            }

            // read ilevel
            if(!_ReadUiObject(ref ui_object, (int)AuctionObjects.OBJECT_TOOLTIP_ILEVEL))
                return false;
 

            ui_text = Globals.mem.ReadMemoryAsString(ui_object.addr2_value, (int)AuctionConst._TEXT_STANDARD_LIMIT).ToString();

            // parse ilevel
            if(ui_object.visible > 0)
            {
                if( string.IsNullOrEmpty(ui_text) || ui_text.Equals(AuctionConst._CLEAR_CHAR))
                    return false;
                Tools.StrFormatRead(ref count, ui_text, AuctionConst._FORMAT_ITEM_ILEVEL, string1, item.ilevel);
            }

            return true;
        }

        //------------------------------------------------------------------------
        bool _ReadHoverItemStats(ref List<Stat> stats)
        {
            UiObject ui_object = new UiObject();
            string ui_text;
            Stat item_stat = new Stat();
            string ptext;

            // read ui object
            if (!_ReadUiObject(ref ui_object, (int)AuctionObjects.OBJECT_TOOLTIP_STATS))
                return false;

            // empty stats
            stats.Clear();

            // if stats available
            if (ui_object.visible > 0)
            {
                ui_text = Globals.mem.ReadMemoryAsString(ui_object.addr2_value, (int)AuctionConst._TEXT_STATS_LIMIT).ToString();

                // parse
                for (ptext = ui_text; (ptext = _ParseItemStatLine(ref item_stat, ptext, false)) != string.Empty && stats.Count <= stats.Capacity; )
                    stats.Add(item_stat);
            }

            return true;
        }

        //------------------------------------------------------------------------
        bool _ReadHoverItemSockets(ref List<Stat> sockets)
        {
            UiObject        ui_object = new UiObject();
            string      ui_text;
            Stat      item_stat = new Stat();

            sockets.Clear();

            // iterate
            for( int i = 0; i < sockets.Count; i++ )
            {
                // read ui object
                if(!_ReadUiObject(ref ui_object, (int)AuctionObjects.OBJECT_TOOLTIP_SOCKET0 + i))
                    return false;

                // only if visible
                if(ui_object.visible > 0)
                {
                    // read socket
                    ui_text = Globals.mem.ReadMemoryAsString(ui_object.addr2_value, (int)AuctionConst._TEXT_STANDARD_LIMIT).ToString();

                    // ignore nulls
                    if( string.IsNullOrEmpty(ui_text) || ui_text.Equals(AuctionConst._CLEAR_CHAR))
                        continue;

                    // parse
                    if(!string.IsNullOrEmpty(_ParseItemStatLine(ref item_stat, ui_text, true)))
                    {
                        // sync gem info
                        Support.SyncGemInfo(ref item_stat);

                        // add to socket collection
                        sockets.Add(item_stat);
                    }
                    else
                        return false;
                }
            }

            return true;
        }

        //------------------------------------------------------------------------
        bool _ClearHoverItemHeaders()
        {
            UiObject ui_object = new UiObject();

            // clear rare/type
            if (!_ReadUiObject(ref ui_object, (int)AuctionObjects.OBJECT_TOOLTIP_TYPE))
                return false;

            if (!Globals.mem.WriteMemoryAsByte(ui_object.addr2_value, (byte)AuctionConst._CLEAR_CHAR))
                return false;

            // clear dps/armor
            if (!_ReadUiObject(ref ui_object, (int)AuctionObjects.OBJECT_TOOLTIP_DPSARMOR))
                return false;

            if (!Globals.mem.WriteMemoryAsByte(ui_object.addr2_value, (byte)AuctionConst._CLEAR_CHAR))
                return false;

            // clear name
            if (!_ReadUiObject(ref ui_object, (int)AuctionObjects.OBJECT_TOOLTIP_NAME))
                return false;

            if (!Globals.mem.WriteMemoryAsByte(ui_object.addr2_value, (byte)AuctionConst._CLEAR_CHAR))
                return false;

            // clear ilevel
            if (!_ReadUiObject(ref ui_object, (int)AuctionObjects.OBJECT_TOOLTIP_ILEVEL))
                return false;
            if (!Globals.mem.WriteMemoryAsByte(ui_object.addr2_value, (byte)AuctionConst._CLEAR_CHAR))
                return false;


            return true;
        }

        //------------------------------------------------------------------------
        bool _ClearHoverItemStats()
        {
            UiObject ui_object = new UiObject();

            // read ui object
            if (!_ReadUiObject(ref ui_object, (int)AuctionObjects.OBJECT_TOOLTIP_STATS))
                return false;

            // write null char
            return Globals.mem.WriteMemoryAsByte(ui_object.addr2_value, (byte)AuctionConst._CLEAR_CHAR);
        }

        //------------------------------------------------------------------------
        bool _ClearHoverItemSockets()
        {
            UiObject ui_object = new UiObject();

            // iterate
            for (int i = 0; i <= ((int)AuctionObjects.OBJECT_TOOLTIP_SOCKET2 - (int)AuctionObjects.OBJECT_TOOLTIP_SOCKET0); i++)
            {
                // read ui object
                if (!_ReadUiObject(ref ui_object, (int)AuctionObjects.OBJECT_TOOLTIP_SOCKET0 + i))
                    return false;

                // write null char
                if (!Globals.mem.WriteMemoryAsByte(ui_object.addr2_value, (byte)AuctionConst._CLEAR_CHAR))
                    return false;
            }

            return true;
        }


        //------------------------------------------------------------------------
        public bool _ReadListRoot(ref AhListRoot listRoot)
        {
            // get list root object from static chain
            uint address = Globals.mem.ReadMemory(_d3_base, AuctionConst._CHAIN_SEARCHRESULTS);

            if (address == 0)
                return false;

            listRoot = Globals.mem.UnsafeReadToStruct<AhListRoot>(address);

            return true;
        }


        //------------------------------------------------------------------------
        public bool _ReadSellRoot(ref AhListRoot listRoot)
        {
            // get list root object from static chain
            uint address = Globals.mem.ReadMemory(_d3_base, AuctionConst._CHAIN_SELLS);
            if (address == 0)
                return false;

            // read list object
            listRoot = Globals.mem.UnsafeReadToStruct<AhListRoot>(address);

            return true;
        }

        //------------------------------------------------------------------------
        bool _UpdateUiRoot()
        {
            UiObject uiObj;

            // if invalid
            if (_ui_root > 0)
            {
                uiObj = Globals.mem.UnsafeReadToStruct<UiObject>(_ui_root);

                if (IsValidUiObject(uiObj, (int)AuctionObjects.OBJECT_ROOT))
                {
                    return true;
                }

            }
            // else update
            else
            {
                // reset
                _ui_root = 0;

                // scan for root
                Globals.mem.Scan();

                // if nothing found then major fail
                if (_ui_root == 0)
                    return false;
            }

            return _ui_root != 0;
        }

        //------------------------------------------------------------------------
        bool _RecurseUiObject(ref UiObject uiObj, ref uint found, uint address, int id, uint depth)
        {
            uint[] children = new uint[AuctionConst.TRAINER_UIOBJECT_LIMIT];

            // sanity check
            if(depth >= AuctionConst.TRAINER_UISEARCH_DEPTH)
                return false;

            // read children
            uiObj = Globals.mem.UnsafeReadToStruct<UiObject>(address);

            // compare path
/*
    #if 1
            if(IsValidUiObject(uiObj, id))
            {
                found = address;
                return true;
            }
    #else
            string text;
            if(object.visible && object.addr2_value && _process.ReadMemory(object.addr2_value, text, _TEXT_STANDARD_LIMIT))
            {
                if(Tools::StrSearch("Your connection to the", text))
                {
                    int x = 33;
                }
            }
    //#endif
 */
            // get child count
            uint count = uiObj.addr1_count;
            if (count == 0 || count >= AuctionConst.TRAINER_UIOBJECT_LIMIT)
                return false;

            // read children
            if (!Globals.mem.ReadMemory(uiObj.addr1_children, children.Length, out children))
                return false;

            // for each child
            for( uint i = 0; i < count; i++ )
                if(_RecurseUiObject(ref uiObj, ref found, children[i], id, depth + 1))
                    return true;

            return false;
        }

        bool _RecurseUiObject(ref UiObject uiObj, ref uint found, int id)
        {
            // get root object address from static chain
            if (!_UpdateUiRoot())
                return false;

            return _RecurseUiObject(ref uiObj, ref found, _ui_root, id, 0);
        }


        //------------------------------------------------------------------------
        public unsafe bool _ReadUiChild(ref UiObject child, string path, ref UiObject parent)
        {

            uint[] address_list = new uint[AuctionConst.UI_COMBO_ROW_LIMIT];

            // read address list
            if (!Globals.mem.ReadMemory(parent.addr1_children, address_list.Length, out address_list))
                return false;

            // locate child in reverse order
            for (int i = (int)parent.addr1_count - 1; i >= 0; i--)
            {
                // read object
                child = Globals.mem.UnsafeReadToStruct<UiObject>(address_list[i]);

                string childPath = string.Empty;

                fixed (byte * pathPointor = child.path)
                {
                    childPath = Marshal.PtrToStringAnsi((IntPtr)pathPointor);
                }
                // check path
                if (childPath.Equals(path))
                    return true;
            }

            return false;
        }

        //------------------------------------------------------------------------
        public unsafe bool _WriteUiObject(int id, bool visible)
        {
            UiObject uiObj = new UiObject();
            uint address = 0;
            uint value = 0;

            if (visible)
            {
                value = 1;
            }
            else
            {
                value = 0;
            }

            // verify
            if (!_ReadUiObject(ref uiObj, id, ref address))
                return false;


            value = (uint)Globals.mem.ReadMemoryAsInt(address + (uint)(Marshal.OffsetOf(typeof(UiObject), "visible")));

            return true;
        }

        //------------------------------------------------------------------------
        public unsafe bool IsValidUiObject(UiObject uiObj, int id)
        {
            return
                uiObj.n1[2] == 0x00000000 &&
                uiObj.n1[3] == 0x00000000 &&
                uiObj.n1[4] == 0x00000000 &&
                uiObj.n1[5] == 0x00000000 &&
                uiObj.n1[6] == 0x00000000 &&
                uiObj.n1[7] == 0xffffffff &&
                uiObj.n1[8] == 0xffffffff &&
                uiObj.n3[3] == 0x600DF00D &&
                Marshal.PtrToStringAnsi((IntPtr)uiObj.path).Equals(AuctionConst._UIOBJECT_PATH[id]);
        }

        //------------------------------------------------------------------------
        void _ParseItemStatString(ref Stat stat, string text)
        {
            // collect values
            stat.values.Clear();
            List<long> numbers = new List<long>();

            if (Tools.StrToNumberList(text, ref numbers, false))
            {
                stat.values.AddRange(numbers);
            }
        }

        //------------------------------------------------------------------------
        private string _ParseItemStatLine(ref Stat stat, string text, bool is_socket)
        {
            string dummy_string = string.Empty;
            string stat_text = string.Empty;
            string pstat_text = stat_text;
            uint count = 0;
            uint i;

            // stop at null or newline
            if (string.IsNullOrEmpty(text))
                return string.Empty;

            // if socket
            if (is_socket)
            {
                // if empty socket
                if ((Tools.StrFormatRead(ref count, text, AuctionConst._FORMAT_SOCKET_EMPTY, pstat_text) > 0 || count != 1) &&
                    // or has gem stats
                    (Tools.StrFormatRead(ref count, text, AuctionConst._FORMAT_SOCKET_GEM, pstat_text) > 0 || count != 1))
                {
                    return string.Empty;
                }
            }
            else
            {
                // read regular item stat (has different formats)
                for (i = 0; i < AuctionConst._FORMAT_STAT.Length && ((count = Tools.StrFormatRead(ref count, text, AuctionConst._FORMAT_STAT[i], dummy_string, dummy_string, pstat_text, dummy_string)) == 0 || count < 2); i++) ;
                if (i == AuctionConst._FORMAT_STAT.Length)
                {
                    return string.Empty;
                }
            }

            // parse stat text
            _ParseItemStatString(ref stat, pstat_text);

            // increment to next item
            text += count;

            return text;
        }

        //------------------------------------------------------------------------
        bool _OnScan(uint address, Byte[] memory, uint length)
        {
            int BufferSize = Marshal.SizeOf(typeof(UiObject));

            UiObject uiObj = (UiObject)LittleBirdLib.Utilities.MemoryHandling.ReadWriteMemory.RawDeserialize(memory, 0, typeof(UiObject));

            if (IsValidUiObject(uiObj, (int)AuctionObjects.OBJECT_ROOT))
            {
                // add address
                _ui_root = address;
                return false;
            }

            return true;
        }


    }
}

